package gbench.sandbox.plot;

import org.junit.jupiter.api.Test;

import gbench.common.fs.FileSystem;
import gbench.common.matlib.MatlibCanvas;
import gbench.common.matlib.MatlibCanvas.NVec;
import gbench.common.matlib.MatlibCanvas.Range;
import gbench.common.matlib.img.ImageWrapper;
import gbench.common.tree.LittleTree.Elem;
import gbench.common.tree.LittleTree.IRecord;
import gbench.sandbox.plot.data.DataReader;

import java.awt.Color;
import java.io.File;
import java.text.NumberFormat;
import java.util.stream.*;

import static java.lang.Math.*;
import static gbench.common.tree.LittleTree.IRecord.*;
import static gbench.common.tree.LittleTree.*;
import static gbench.common.matlib.img.ImageOps.*;

/**
 * 绘制点线图<br>
 * 绘图的的关键 就是构造出合适的运算概念，这个概念必须是成体系的。<br>
 * 即 既需要足够的基本，独立，且精简 又能够 相互联系，可以给予进一步的组合成更为复杂的概念，<br>
 * 并使得这这种组合概念可以逐渐的趋近目标结果,直至完成目的。这就是设计思想的精髓：模块化的概念的组合<br>
 * 透过现象看本质,从基本之中找联系，构造关联，形成结构，最后组织成统一的整体(目标)。<br>
 * <br>
 * 数据结构是概念,函数是组织关联(映射)。<br>
 * f(基本概念)->高级概念。这就是设计。
 * f...f(f(基本概念)) f的层层嵌套就构成 实现从基本到简单，由简单到复杂的 组合结构（解析结构)<br>
 * 所以设计的就是组织，组织 就是 组合(关联&映射)的概念结构。
 * @author gbench
 *
 */
public class JunitPlotLines {
    
    /**
     * 绘制点线图
     * @param dataframe DataFrame 结构的IRecord,IRecord每个都是一个列向量。
     */
    public void point_lines(final IRecord dataframe) {
        
         // 环境数据
        final var size = 1000;// 绘图的防范尺寸[0,size)
        final var matlib = new MatlibCanvas(size); // 构造绘图环境,canvas
        
        // 基础数据
        final var x_rng = Range.of2(100, 450);// x 的绘图区域范围
        final var y_rng = Range.of2(50, 450);// y 的绘图区域范围
        final var dfx = dataframe.lla("x", Double.class); // 数据空间的 水平采样点
        final var dfy = dataframe.lla("y", Double.class); // 数据空间的 垂直采样点
        final var x_transformer = DataReader.transformer(x_rng, dfx); // 水平坐标转换器
        final var y_transformer = DataReader.transformer(y_rng, dfy); // 垂直坐标转换器
        final var rhotetas = Range.cph(Range.of2(0, 5).limit(10), Range.of2(-PI, PI).limit(40))
            .collect(Collectors.toList()); // 小圆点的绘制向量长度rho与角度theta
        
        // 绘图数据
        final var xs = NVec.of(dfx.stream().map(x_transformer)); // 转换成绘图水平坐标的点 :x values
        final var ys = NVec.of(dfy.stream().map(y_transformer)); // 转换成绘图垂直坐标的点 :y values
        final var centers = LIST(ZIP("x,y", xs.list(),ys.list()).stream() // 组合成点结构的圆心序列
            .map(rec->NVec.of(rec.toArray(Double.class))).map(matlib::xY));// cartesian形式的圆心位置序列
        
        // 绘制趋势线 的参数：即回归算法,注意由于是绘制直线，所以这里的算法采用的 绘图空间数据，而不是业务数据模型。
        // 所以这里使用的模型不是 业务的回归模型。
        final var xb = xs.mean(); // x 的平均数,x bar
        final var yb = ys.mean(); // y 的平均数 y bar
        final var xd = xs.sub(xb); // x的离差 x dispersion
        final var b1 = xs.sub(xb).dot(ys.sub(yb)) / xd.dot(xd); // 趋势线的斜率
        final var b0 = yb - b1 * xb;// 趋势线的截距
        
        // 开始直线绘图
        matlib.plot(size, size / 2, pen -> {
            // 基础的绘图参数
            IRecord.slidingS(centers, 2, 1, true).forEach(pen::drawStraightLine);// 将序列剪裁收尾相连的选段序列，并连接成线。
            pen.drawPoints(Elem.cph2(rhotetas, centers).map(matlib::rhotheta_affine_offset)); // 绘制图像的点
            pen.setColor(clr2rgb(Color.green)).drawLine(x -> b0 + b1 * x);// 绘制回归线
        }, null, true, img -> {// 坐标轴的绘制
            final var g = new ImageWrapper(img, true);// 包装图片类
            final var margin = 50; // 边际余量单位,用于绘制坐标轴的延展方向。
            final var ytick_offset = 30; // y轴刻度边际保留
            final var xtick_offset = -13; // x轴刻度边际保留
            final var nf = NumberFormat.getInstance(); // 数据格式化器
            
            // x水平轴
            g.setColor(Color.red);
            g.drawHorizontalLine(x_rng.offset(-margin,margin), y_rng.start()); // X水平轴
            xs.foreach((i, x) -> {g.drawString(nf.format(dfx.get(i)), x, y_rng.start() + xtick_offset);}); // 水平轴上的 业务数据值
            
            // 水平轴的刻度-标准
            g.setColor(Color.white);
            final var xdtf = DataReader.detransformer(x_rng, dfx); // 绘图空间到数据空间的变换
            x_rng.offsetEnd(3 * margin).limit(6).forEach(x -> {
                g.drawString(nf.format(xdtf.apply(x)), x, y_rng.start() + 3 * xtick_offset);
            });
            
            // y垂直轴
            g.setColor(Color.red);
            g.drawVerticalLine(y_rng.offset(-margin,margin), x_rng.getStart()); // 垂直轴
            ys.foreach((i, y) -> {g.drawString(nf.format(dfy.get(i)), x_rng.start() - ytick_offset, y);}); // 垂直轴上的 业务数据值
            
            // 垂直轴的标准刻度
            g.setColor(Color.white);
            final var ydtf = DataReader.detransformer(y_rng, dfy); // 绘图空间到数据空间的变换
            y_rng.offsetEnd(margin).limit(10).forEach(y -> {
                g.drawString(nf.format(ydtf.apply(y)), x_rng.start() - 3 * ytick_offset, y);
            });
            
        });// plot
    }

    /**
     * 数据绘图
     */
    @Test
    public void plot() {
        final var datafile = FileSystem.path("data/data.txt", this.getClass());
        final var rec = DataReader.load(new File(datafile)).rec("表6-4");
        System.out.println(rec.toString2());
        
        //绘制点线图
        final var dfx = rec.lla(0, (String e) -> Double.valueOf(e)); // 读取水平轴的采样点数据x
        final var dfy = rec.lla(1, (String e) -> Double.valueOf(e)); // 读取垂直轴的采样点数据y
        point_lines(REC("x", dfx, "y", dfy)); // 把dfx,dfy拼接成DataFrame格式
        point_lines(REC("y", dfx, "x", dfy)); // 把dfx,dfy拼接成DataFrame格式,颠倒一下x,y的方向
    }
    
    /**
     * 启动函数
     * @param args
     */
    public static void main(String args[]) {
        new JunitPlotLines().plot();
    }

}
